'''
Created on 03/05/2011

@author: chips
'''
from chips.types.dictionaries import frozendict
import collections
import functools
import itertools
import json as _json
import weakref

__all__ = 'flatten walk copy'.split()

class JSONKeyError(Exception):
    pass

class GenericContainerInterface(collections.Sized, collections.Iterable):
    def __init__(self, root):
        self._root_schema = root

    def adapt_array(self, obj):
        """Adapt object of type JSON Array before it is returned. The default 
        implementation wraps the object in a uniform interface that accepts
        JSON methods and idioms."""

        return Array(obj)

    def adapt_object(self, obj):
        """Adapt object of type JSON Object before it is returned. The default 
        implementation wraps the object in a uniform interface that accepts
        JSON methods and idioms."""

        return Object(obj)

    def adapted(self, obj):
        """Adapt any JSON type before it is returned. If obj is a JSON array or 
        object, the default implementation wraps the object in a uniform 
        interface that accepts JSON methods and idioms. Otherwise 'obj' is 
        returned."""

        print 'adapting ', type(obj), obj
        tt = type(obj)
        code = json_type_code(tt)
        if code & CONTAINER_T:
            if (code & ARRAY_T) == ARRAY_T:
                return self.adapt_array(obj)
            elif (code & OBJECT_T) == OBJECT_T:
                return self.adapt_object(obj)
        else:
            return obj

    @property
    def root(self):
        return self._root_schema

    # Abstract methods ---------------------------------------------------------
    def __len__(self):
        return len(self._root_schema)

    def __iter__(self):
        adapted = self.adapted
        return (adapted(item) for item in iter(self._root_schema))

    def __getitem__(self, idx):
        if isinstance(idx, tuple):
            curr = self
            try:
                for k in idx:
                    curr = curr._fast_getitem(k)
            except AttributeError:
                raise JSONKeyError('$.' + '.'.join(idx) + ' does not exists')

            return curr
        else:
            return self._fast_getitem(idx)

    def _fast_getitem(self, idx):
        try:
            raw = self._root_schema[idx]
        except (KeyError, IndexError) as ex:
            raise ex
        else:
            return self.adapted(raw)

    def __setitem__(self, k, v):
        # Prevents from assigning the interface object to keys/indices
        if isinstance(v, GenericContainerInterface):
            v = v.root

        try:
            self._root_schema[k] = v
        except (KeyError, IndexError) as ex:
            raise ex

    def __delitem__(self, k):
        del self._root_schema[k]

    # Special purpose/efficient implementations of Mixin methods ---------------
    def __contains__(self, obj):
        '''
        If obj is an interface, tests for the inclusion of the root element. 
        '''
        if isinstance(obj, GenericContainerInterface):
            return obj.root in self._root_schema
        else:
            return obj in self._root_schema

    # Additional methods -------------------------------------------------------
    def pretty(self, oneline=False, indent=0):
        pass

class Array(GenericContainerInterface, collections.MutableSequence):
    def insert(self, idx, v):
        if isinstance(v, GenericContainerInterface):
            v = v.root
        self._root_schema.insert(idx, v)

    def __str__(self):
        lst = []
        length = 2
        for item in self:
            lst.append(str(item))
            length += len(lst[-1]) + 2
            if length > 80:
                lst.append('...')
                lst.append(self[-1])
                break
        return '[' + ', '.join(lst) + ']'

    def pretty(self, oneline=False, indent=0):
        items = []
        indent = ' ' * indent

        for item in self:
            try:
                printer = items.pretty
            except AttributeError:
                items.append(str(item))
            else:
                items.append(printer(oneline))

        if oneline:
            return indent + '[' + ', '.join(items) + ']'
        else:
            pretty = '[ ' + ',\n  '.join(items) + ' ]'
            pretty = [ indent + line for line in pretty.splitlines() ]
            return '\n'.join(pretty)

class Object(GenericContainerInterface, collections.MutableMapping):
    pass


a = Array([1, 2, 3, [1, 1, 1, 1]])
print a[3, 2]
print a[3, 2, 3]
print a
print a.pretty()

def flatten(json, base='$'):
    return dict(walk(json, '$'))

def as_json(obj):
    '''Return obj if it is a JSON structure, or try conversion if it is not.'''

    if is_json(obj, convert=False):
        return obj
    else:
        return json_copy(obj)

def is_json(obj, convert=False):
    '''Return True if 'obj' is a valid JSON structure. 
    
    @param obj:
    @param convert:
    '''
    print('is_json is not implemented')
    return True

def is_json_ext(obj):
    raise NotImplementedError

if __name__ == '__main__':
    import doctest
    doctest.testmod(optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
